﻿#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

//数组名的理解
    //在之前的学习中我们已经知道：数组名本来就是地址，而且是数组首元素的地址
//int main()
//{
//	int arr[10] = { 0 };
//	int* p1 = arr;
//	int* p2 = &arr[0];
//	printf("arr     = %p\n", p1);
//	printf("&arr[0] = %p\n", p2);
//	//由打印的值我们可以证明：数组名就是数组首元素的地址
//
//	printf("%zd\n", sizeof(arr)); //40
//	//但上面一条代码得到的值又让我们产生了一个疑问：如果数组名就像我们说的是数组首元素的地址
//	//那得到的值应该是4/8(根据所在环境不同指针类型大小不同)
//	return 0;
//}

    //其实数组名就是数组首元素的地址这句话是对的，但是有两个例外：
	//(1)sizeof(数组名)，sizeof中单独放数组名，这⾥的数组名表示的是整个数组，计算的是整个数组的大小，单位是字节
    //(2) &数组名，这里的数组名表示整个数组，取出的是“整个数组的地址”（整个数组的地址和数组首元素的地址是有区别的）
    //除此之外，任何地方使用数组名，数组名都表示首元素的地址。
//int main()
//{
//	int arr[10] = { 0 };
//	/*printf("arr     = %p\n", arr);
//	printf("&arr[0] = %p\n", &arr[0]);
//	printf("&arr    = %p\n", &arr);*/
//	//又打印的结果我们会发现：这三者的值是完全一样的，可是我们说数组名取地址取出来的是整个数组的地址，为什么还是一样的呢？
//	//原因是不管是取出整个数组的地址还是数组首元素地址，都是从数组首元素开始的，故在打印的时候所看到的值的确一样
//	//那我们怎么判断数组名取地址的确取出的是整个数组的地址呢？我们可以利用之前所学的知识：地址 +- 整数
//	printf("arr       = %p\n", arr);         //0056FB0C
//	printf("arr+1     = %p\n", arr + 1);     //0056FB10
//	printf("&arr[0]   = %p\n", &arr[0]);     //0056FB0C
//	printf("&arr[0]+1 = %p\n", &arr[0] + 1); //0056FB10
//	printf("&arr      = %p\n", &arr);        //0056FB0C
//	printf("&arr+1    = %p\n", &arr + 1);    //0056FB34
//	//从一次运行的结果我们发现&arr[0]和&arr[0]+1相差4个字节，arr和arr+1也相差4个字节，
//	//是因为&arr[0]和arr都是首元素的地址，+1就是跳过一个元素，而两者都是 int* 类型，故跳过一个元素则是跳过4个字节
//	//但是&arr和&arr+1我们发现相差40个字节，这就是因为&arr是数组的地址，+1操作是跳过整个数组的大小
//	//这也变相的说明了&arr的类型并非是 int* 类型
//	return 0;
//}

//使用指针访问数组
//int main()
//{
//    int arr[10] = { 0 };
//    //使用指针访问数组
//    int sz = sizeof(arr) / sizeof(arr[0]);
//    //输入10个值
//    int* p = arr;
//    for (int i = 0; i < sz; i++)
//    {
//        scanf("%d", p + i); //p + i == &arr[i] == arr + i
//    }
//    //输出10个值
//    for (int i = 0; i < sz; i++)
//    {
//        printf("%d ", *(p + i)); //*(p + i) == *(arr + i) == arr[i]
//    }                            //由于*(arr + i) == arr[i] ==> *(p + i) == p[i]
//    return 0;
//}
    //数组与指针的关系：
    //数组就是数组，是一块连续的空间(数组的大小和数组的元素个数和数组类型都有关系)
    //指针(变量)就是指针(变量)，是一个变量(大小为4/8个字节)
    //数组名是地址，是首元素的地址
    //可以使用指针来访问数组

//⼀维数组传参的本质
    //数组传参的时候，形参的确是可以写成数组的形式的，也是为了刚接触数组传参时自然而然地去理解
    //但本质上还是指针变量，原因就是函数调用时的数组名不属于两个例外，即数组名就是首元素的地址
//void Print(int arr[10]) //int* arr
//{
//    //int sz = sizeof(arr) / sizeof(arr[0]); // 这样是得不到数组的元素个数
//    //虽然看上去是数组的其中一个例外，但这里的arr并非是数组名，而只是指针变量，指针变量的大小为4/8个字节
//    int i = 0;
//    for (i = 0; i < 10; i++)
//    {
//        printf("%d ", arr[i]);
//    }
//}

//void Print(int* p, int sz) //数组传参就应该写成这个样子的函数，上面的函数只是为了刚接触好理解也不是错的
//{
//    int i = 0;
//    for (i = 0; i < sz; i++)
//    {
//        printf("%d ", *(p + i));
//    }
//}
//int main()
//{
//    int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//    int sz = sizeof(arr) / sizeof(arr[0]); //这个表达式只能写在函数外面
//    Print(arr, sz); //arr 数组名就是数组首元素的地址，而如果实参是地址，形参也应该是写成(int* p)的形式
//    return 0;
//}

//冒泡排序
    //冒泡排序的核心思想就是：两两相邻的元素进行比较。
//void bubble_sort(int* arr, int sz)
//{
//    int i = 0;
//    //趟数
//    for (i = 0; i < sz - 1; i++) //十个元素需要9趟
//    {
//        int j = 0;
//        //一趟排序的过程
//        for (j = 0; j < sz - i - 1; j++)
//        {
//            if (arr[j] > arr[j + 1]) //两两相邻的元素进行比较
//            {
//                arr[j + 1] = arr[j] + arr[j + 1];
//                arr[j] = arr[j + 1] - arr[j];
//                arr[j + 1] = arr[j + 1] - arr[j]; //运用了一下之前所学方法：不用创建新变量来改变两个数的位置
//            }
//        }
//    }
//}

    //上面的排序代码虽然已经满足了冒泡排序的逻辑，但不难发现当中途某趟如果已经完成了升序或降序
    //但循环仍然在进行，虽然不会进入if语句，但仍需要进行后续比较，这是没必要的，所以以下是进行的优化：
//void bubble_sort(int* arr, int sz)
//{
//    int i = 0;
//    for (i = 0; i < sz - 1; i++)
//    {
//        //假设已经完成了排序
//        int flag = 1;
//        int j = 0;
//        for (j = 0; j < sz - i - 1; j++)
//        {
//            if (arr[j] > arr[j + 1])
//            {
//                arr[j + 1] = arr[j] + arr[j + 1];
//                arr[j] = arr[j + 1] - arr[j];
//                arr[j + 1] = arr[j + 1] - arr[j];
//                flag = 0; //如果进入了if语句则说明当前趟仍需要排序，则假设不成立
//            }
//        }
//        if (flag == 1) //如果一趟下来if语句一直没有满足，则说明上一趟已经完成了排序，后续不需要在进行判断
//        {
//            break;
//        }
//    }
//}
//int main()
//{
//    int arr[10] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
//    int sz = sizeof(arr) / sizeof(arr[0]);
//    //排序
//    bubble_sort(arr, sz);
//    for (int i = 0; i < sz; i++)
//    {
//        printf("%d ", arr[i]);
//    }
//    return 0;
//}

//⼆级指针
    //指针变量也是变量，是变量就有地址，那指针变量的地址存放在哪⾥？ --> 这就是二级指针
//int main()
//{
//    int a = 0;
//    int* p = &a; //p就是一级指针
//    int** pp = &p; //pp就是二级指针
//    //和之前拆开理解一级指针 int* p 一样，int** pp也能拆开来理解：
//    //int * * pp --> 和pp紧挨着的*所指代意思为 pp 变量为指针变量
//    //           --> 前面的 int * 看成一个整体，之前 int* p 的int我们理解为是p所指向的对象类型为int类型
//    //               在这里也能这么理解：pp所指向的对象(p)类型为 int* 类型
//    return 0;
//}

//⼆级指针的运算
//int main()
//{
//    int a = 10;
//    int* p = &a;
//    int** pp = &p;
//    printf("%p\n", *pp);
//    printf("%p\n", &a);
//    // *pp 通过对pp中的地址进行解引用，这样找到的是p，而p就是&a，所以上面两个代码输出结果是一样的
//    printf("%d\n", **pp); //10
//    // **pp 先通过 *pp 找到 p ,然后对 p 进行解引用操作：*p ，那找到的是a
//    return 0;
//}

//指针数组
    //指针数组是指针还是数组？
    //我们类比⼀下，整型数组，是存放整型的数组，字符数组是存放字符的数组。
    //那指针数组呢？其实就是存放指针的数组。指针数组的每个元素都是用来存放地址（指针）的。
//指针数组模拟⼆维数组
//int main()
//{
//    int arr1[] = { 1, 2, 3, 4, 5 };
//    int arr2[] = { 2, 3 ,4 ,5 ,6 };
//    int arr3[] = { 3 ,4 ,5 ,6 ,7 };
//    int* arr[] = { arr1, arr2, arr3 }; // == { &arr1[0], &arr2[0], &arr3[0] }
//    //由于不属于两个例外，这里的数组名即为数组首元素的地址，故arr为指针数组
//    //打印指针数组
//    for (int i = 0; i < 3; i++)
//    {
//        for (int j = 0; j < 5; j++)
//        {
//            printf("%d ", arr[i][j]);
//     //虽然这里看上去是打印二维数组的样子，但arr并非是二维数组
//     //arr[i]是访问arr数组的元素，arr[i]找到的数组元素指向了其中一个整型一维数组，
//     //arr[i][j]是指向其中一个整型⼀维数组中下标为j的元素。
//        }
//        printf("\n");
//    }
//    return 0;
//}
    //上述的代码虽然能模拟出二维数组的效果，但实际上并非完全是⼆维数组，因为每一行并非是连续的。